﻿using System;
using System.Diagnostics;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using WindowsPhoneEssentials.Helpers;
using WindowsPhoneEssentials.Commands;
using WindowsPhoneMVC.Navigation;

namespace WindowsPhoneMVC.Commands
{
    public class Navigate
    {
        public static readonly DependencyProperty ToProperty =
            DependencyProperty.RegisterAttached("To", typeof (string), typeof (Navigate), new PropertyMetadata(default(string), ToChanged));

        public static readonly DependencyProperty OnItemSelectedProperty =
            DependencyProperty.RegisterAttached("OnItemSelected", typeof (string), typeof (Navigate), new PropertyMetadata(default(string), OnItemSelectedChanged));

        public static readonly DependencyProperty OnSelectionProperty =
            DependencyProperty.RegisterAttached("OnSelection", typeof (string), typeof (Navigate), new PropertyMetadata(default(string), OnSelectionChanged));

        private static void OnSelectionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var selector = d.Ancestors<Selector>().FirstOrDefault();
            if (selector != null)
            {
                selector.Loaded += ItemSelectorLoaded;
            }
        }

        private static void ItemSelectorLoaded(object sender, RoutedEventArgs e)
        {
            var selector = (Selector)sender;
            var command = GetOnItemSelected(selector);
            selector.Loaded -= SelectorLoaded;
            var viewModel = GetViewModel(selector, command);

            if (viewModel == null)
            {
                Debug.WriteLine("Cannot find a DataContext of type ViewModelBase in visual tree to bind Navigate.OnItemSelected=\"{0}\"", command);
                return;
            }

            viewModel.AddSetupTask(() => selector.SelectionChanged += SelectorItemSelectionChanged);
            viewModel.AddCleanupTask(() => selector.SelectionChanged -= SelectorItemSelectionChanged);
        }

        private static void SelectorItemSelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            var selector = (Selector)sender;
            var selectedItem = selector.SelectedItem as UIElement;
            if (selectedItem == null) return;

            var command = GetOnSelection(selectedItem);

            if (e.AddedItems.Count != 1) return;
            var viewModel = GetViewModel(selector, command);

            string controller;
            string action;
            if (viewModel != null && ParseCommand(command, out controller, out action))
                viewModel.Navigator.Invoke(controller, action, e.AddedItems[0]);
            selector.SelectedIndex = -1;
        }

        private static void OnItemSelectedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var selector = (Selector) d;
            selector.Loaded += SelectorLoaded;
        }

        static void SelectorLoaded(object sender, RoutedEventArgs e)
        {
            var selector = (Selector)sender;
            var command = GetOnItemSelected(selector);
            selector.Loaded -= SelectorLoaded;
            var viewModel = GetViewModel(selector, command);

            if (viewModel == null)
            {
                Debug.WriteLine("Cannot find a DataContext of type ViewModelBase in visual tree to bind Navigate.OnItemSelected=\"{0}\"", command);
                return;
            }

            viewModel.AddSetupTask(() => selector.SelectionChanged += SelectorSelectionChanged);
            viewModel.AddCleanupTask(() => selector.SelectionChanged -= SelectorSelectionChanged);
        }

        static void SelectorSelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            var selector = (Selector)sender;
            var command = GetOnItemSelected(selector);

            if (e.AddedItems.Count != 1) return;
            var viewModel = GetViewModel(selector, command);

            string controller;
            string action;
            if (viewModel != null && ParseCommand(command, out controller, out action))
                viewModel.Navigator.Invoke(controller, action, e.AddedItems[0]);
            selector.SelectedIndex = -1;
        }

        public static void SetOnItemSelected(Selector element, string value)
        {
            element.SetValue(OnItemSelectedProperty, value);
        }

        public static string GetOnItemSelected(Selector element)
        {
            return (string) element.GetValue(OnItemSelectedProperty);
        }

        public static void SetOnSelection(UIElement element, string value)
        {
            element.SetValue(OnSelectionProperty, value);
        }

        public static string GetOnSelection(UIElement element)
        {
            return (string)element.GetValue(OnSelectionProperty);
        }

        private static void ToChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var newValue = (string)e.NewValue;

            if (e.NewValue == DependencyProperty.UnsetValue)
                d.ClearValue(Click.CommandProperty);
            else
            {
                string controller;
                string action;
                if (ParseCommand(newValue, out controller, out action))
                    Click.SetCommand((ButtonBase)d, new DelegateCommand(() => NavigateCommand(d, newValue, controller, action)));
            }
        }

        public static bool ParseCommand(string commandText, out string controller, out string action)
        {
            var actionParts = commandText.Split('.');
            if (actionParts.Length != 2)
            {
                Debug.WriteLine("Navigate.To value must be in Controller.Action format");
                controller = null;
                action = null;
                return false;
            }

            controller = actionParts[0];
            action = actionParts[1];
            return true;
        }

        private static void NavigateCommand(DependencyObject d, string command, string controller, string action)
        {
            var viewModel = GetViewModel(d, command);

            if (viewModel == null) return;

            var dataContext = ((FrameworkElement)d).DataContext;
            //Only pass datacontext if it isn't a viewmodel
            if (dataContext is ViewModelBase)
                viewModel.Navigator.Invoke(controller, action);
            else
                viewModel.Navigator.Invoke(controller, action, dataContext);
        }

        private static ViewModelBase GetViewModel(DependencyObject d, string command)
        {
            var viewModel = d.AncestorsAndSelf()
                .OfType<FrameworkElement>()
                .Select(a => a.DataContext)
                .OfType<ViewModelBase>()
                .FirstOrDefault();

            //Cant find any element in visual tree with viewmodel of ViewModelBase
            if (viewModel == null)
            {
                Debug.WriteLine("Cannot find a DataContext of type ViewModelBase in visual tree to bind Navigate.To=\"{0}\"", command);
                return null;
            }
            return viewModel;
        }

        public static void SetTo(ButtonBase element, string value)
        {
            element.SetValue(ToProperty, value);
        }

        public static string GetTo(ButtonBase element)
        {
            return (string) element.GetValue(ToProperty);
        }
    }
}
